package com.googlecode.bip.servlet.filter;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;

/**
 * How to configure:
 * <p>
 * Use JVM Properties to configure or System environment variable (Filter check
 * properties first, if not found it check environment variables)
 * </p>
 * <p>
 * To activate filter define: -DQxUrlRewriteFilterEnable=true
 * </p>
 * <p>
 * To set a new root directory define: -DQxUrlRewriteFilterRoot=/path/to/your/real/root
 * </p>
 * Then you can define map rewrite in your web.xml
 * <p>
 *  &lt;init-param&gt;<br/>
 *   &lt;param-name&gt;/web/path&lt;/param-name&gt;<br/>
 *   &lt;param-value&gt;/relative/path/for/mapping&lt;/param-value&gt;<br/>
 *  &lt;/init-param&gt;
 * </p>
 *
 *
 */
public class QxUrlRewriteFilter implements Filter
{
    private final static Logger logger = Logger.getLogger( QxUrlRewriteFilter.class );
    private ServletContext context;
    private final HashMap<String,File> rewrites = new HashMap<String,File>();

    /**
     * System property or environment variable {@value} must be contain true to enable filtering.
     */
    public final static String QXURLREWRITEFILTER_ENABLE = "QxUrlRewriteFilterEnable";

    /**
     * System property environment variable {@value} could contain value
     * to overwrite {@link ServletContext#getRealPath(String)} for root.
     */
    public final static String QXURLREWRITEFILTER_ROOT = "QxUrlRewriteFilterRoot";

    private String getInitParameter( final String propertyName )
    {
        String value;

        try {
            value = System.getProperty( propertyName );
            }
        catch( SecurityException e ) {
            logger.warn( "Can't get System property '" + propertyName + "'" , e);
            value = null;
            }

        if( value == null ) {
            try {
                value = System.getenv( propertyName );
                }
            catch( SecurityException e ) {
                logger.warn( "Can't get System environment variable '" + propertyName + "'" , e);
                }
            }

        logger.info( "'" + propertyName + "'" + value );
        return value;
    }

    private boolean isUrlRewriteFilterEnable()
    {
        return Boolean.valueOf(
            getInitParameter( QXURLREWRITEFILTER_ENABLE )
            ).booleanValue();
    }

    private String getForceRoot()
    {
        return getInitParameter( QXURLREWRITEFILTER_ROOT );
    }

    /**
     *
     * @param config
     * @throws ServletException
     */
    @Override
    public void init( final FilterConfig config )
        throws ServletException
    {
        context = config.getServletContext();

        logger.warn( "QxUrlRewriteFilter is used, this Filter is design to be disable in production !" );

        if( !isUrlRewriteFilterEnable() ) {
            logger.warn(
                "QxUrlRewriteFilter is not enabled, set -D"
                    + QXURLREWRITEFILTER_ENABLE
                    + "=true on JVM command line to enable it."
                    );
            return;
            }

        logger.warn( "QxUrlRewriteFilter enabled" );

        final File      root;
        final String    forceRoot = getForceRoot();

        if( forceRoot != null ) {
            root = new File( forceRoot ).getAbsoluteFile();

            logger.info( "Force root to : " + root );
            }
        else {
            root = new File( context.getRealPath("/") );

            logger.info( "use default root : " + root );
            }

        for( Enumeration<String> enm = config.getInitParameterNames(); enm.hasMoreElements(); ) {
            final String path  = enm.nextElement();
            final File   mapTo = new File( root, config.getInitParameter( path ) );

            logger.info( "Map '" + path + "' to '" + mapTo + "'" );

            if( !mapTo.exists() ) {
                final String msg = "Can not find [" + mapTo + "] for mapping [" + path + "]";

                logger.fatal( msg );
                //throw new ServletException( msg );
                }

            rewrites.put( path, mapTo );
            }
    }

    /**
     *
     */
    @Override
    public void destroy()
    {
        //empty
    }

    /**
     *
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(
            ServletRequest  servletRequest,
            ServletResponse servletResponse,
            FilterChain     filterChain
            )
        throws IOException, ServletException
    {
        HttpServletRequest  request  = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;

        // Look for files to process from here
        String uri = request.getRequestURI().substring(request.getContextPath().length());

        for( String path : rewrites.keySet() ) {
            if( uri.startsWith(path) ) {
                // It's got to match a path (i.e. not part of a path)
                if (path.length() == uri.length() || uri.charAt(path.length()) != '/') {
                    continue;
                    }

                // Find the file
                File file = new File(rewrites.get(path), uri.substring(path.length()));
                //System.out.println(file.getAbsolutePath());
                if (!file.exists()) {
                    continue;
                    }

                // Check timestamps
                response.setDateHeader("Last-Modified", file.lastModified());
                long lastMod = request.getDateHeader("If-Modified-Since");
                if (lastMod > 0 && Math.abs(file.lastModified() - lastMod) < 1000) {
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    return;
                }

                // Send the file
                String mimeType = context.getMimeType(file.getName());
                response.setContentType(mimeType);
                response.setContentLength((int)file.length());

                // Output
                OutputStream os = response.getOutputStream();
                FileInputStream fis = new FileInputStream(file);
                byte[] buffer = new byte[8*1024];
                int len;
                while ((len = fis.read(buffer)) > -1) {
                    os.write(buffer, 0, len);
                    }
                os.flush();
                os.close();
                fis.close();

                // Done
                return;
            }
        }

        filterChain.doFilter( request, response );
    }
}
